// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <algorithm>

#include "Platform/System.h"

#include "Platform/PathEqual.h"

namespace Platform {

template <typename CharT>
static std::basic_string<CharT> ToLower(std::basic_string<CharT> s)
{
    std::transform(s.begin(), s.end(), s.begin(),
        [](CharT ch) {
            if constexpr (sizeof(CharT) == sizeof(char)) {
                return static_cast<CharT>(std::tolower(static_cast<unsigned char>(ch)));
            } else {
                return static_cast<CharT>(std::towlower(static_cast<wchar_t>(ch)));
            }
    });
    return s;
}

// Manual lexical normalization for paths, compatible with experimental::filesystem.
// This removes "." components and resolves ".." when possible, without touching the real FS.
static Path LexicallyNormalCompat(const Path& in)
{
#if __has_include(<filesystem>)
    // On C++17, prefer the standard member function.
    return in.lexically_normal();
#else
    // Manual normalization for experimental::filesystem.
    // Algorithm: split into components, keep root_name and root_directory flags,
    // fold "." away, and resolve ".." against the previous normal component when allowed.
    std::vector<Path> stack;
    const auto rootName = in.root_name();          // e.g., "C:" on Windows
    const auto hasRootDir = in.has_root_directory(); // whether the path has a root slash

    for (const auto& part : in) {
        if (part == ".") {
            continue;
        }
        if (part == "..") {
            if (!stack.empty() && stack.back() != "..") {
                // Pop a normal component if available and not crossing root.
                if (!(stack.size() == 1 && hasRootDir)) {
                    stack.pop_back();
                    continue;
                }
            }
            // Keep ".." if we cannot resolve it.
            stack.push_back(part);
            continue;
        }
        if (part == rootName || part == "/") {
            // Skip: we will re-add root below.
            continue;
        }
        stack.push_back(part);
    }

    Path out = in.root_path();
    for (const auto& seg : stack) {
        out /= seg;
    }
    if (out.empty()) {
        out = ".";
    }
    return out;
#endif
}

static Path WeaklyCanonicalCompat(const Path& in, std::error_code& ec)
{
    ec.clear();
#if __has_include(<filesystem>)
    Path wk = FileSystem::weakly_canonical(in, ec);
    if (!ec) {
        return wk.lexically_normal();
    }
    // Fall back if weakly_canonical failed.
    return in.lexically_normal();
#else
    // experimental::filesystem does not provide weakly_canonical nor lexically_normal.
    // We emulate a "weak" canonicalization by returning a lexically normalized absolute path.
    (void)ec; // not used in this branch
    return LexicallyNormalCompat(in);
#endif
}

// Make a path comparable across platforms:
// 1) Make it absolute (relative to executable directory as in your original code).
// 2) Apply best-effort canonicalization (weak when available).
static Path MakeComparablePath(const Path& in)
{
    std::error_code ec;

    Path abs = in;
    if (!abs.is_absolute()) {
        abs = System::GetExecutablePath().parent_path() / in;
    }

    Path wk = WeaklyCanonicalCompat(abs, ec);
    return wk; // already normalized by compat function
}

bool PathEqual(const Path &left, const Path &right)
{
    // First, normalize both sides to comparable absolute forms.
    const Path l = MakeComparablePath(left);
    const Path r = MakeComparablePath(right);

    // If both paths exist, prefer filesystem-level equivalence (resolves symlinks/hardlinks).
    std::error_code e1;
    std::error_code e2;
    const bool lExists = exists(l, e1);
    const bool rExists = exists(r, e2);
    if (!e1 && !e2 && lExists && rExists) {
        std::error_code ee;
        const bool same = equivalent(l, r, ee);
        if (!ee) {
            return same;
        }
    }

#if defined(PLATFORM_WINDOWS)
    // Use native wide strings for best fidelity on Windows.
    const auto ln = ToLower(l.native());
    const auto rn = ToLower(r.native());
    return ln == rn;
#else
    return l == r;
#endif
}

} // namespace Platform
